/* $Id: SvgaHostObjects.cpp $ */
/** @file
 * VirtualBox Windows Guest Mesa3D - VMSVGA host object accounting.
 */

/*
 * Copyright (C) 2016-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 */

#include "gallium/Svga.h"
#include "gallium/svga3d_types.h"
#include "gallium/SvgaFifo.h"
#include "asm.h"
#include "trace.h"
#include "avl.h"
#include "VBoxMPWddm.h"

static UINT32 g_DecoderCnt = 0;

/*
 * Host objects are resources which created or allocated by the guest on the host.
 *
 * The purpose of the host objects is to:
 * 1) make sure that a host resource is not deallocated by the driver
 *    while it is still being used by the guest.
 * 2) store additional information about an object, for example a shared sid
 *    for the surfaces.
 *
 * Currently this applies only to the SVGA surfaces. The user mode driver can
 * submit a command buffer which use a surface and then delete the surface, because
 * the surface is not needed anymore.
 * The miniport driver check command buffers and adds a reference for each surface.
 * When a surface is deleted it will still be referenced by the command buffer and
 * will be deleted only when the buffer is processed by the host.
 */

/** Return a host object with the given key.
 *
 * @param pSvga    Device instance.
 * @param u32Key   Host object key.
 * @return The pointer to the host object with increased reference counter.
 */
static SVGAHOSTOBJECT *svgaHostObjectQuery(VBOXWDDM_EXT_VMSVGA *pSvga,
                                           AVLU32TREE *pAvlTree,
                                           UINT32 u32Key)
{
    KIRQL OldIrql;
    SvgaHostObjectsLock(pSvga, &OldIrql);
    SVGAHOSTOBJECT *pHO = (SVGAHOSTOBJECT *)RTAvlU32Get(pAvlTree, u32Key);
    if (pHO)
       ASMAtomicIncU32(&pHO->cRefs);
    SvgaHostObjectsUnlock(pSvga, OldIrql);
    return pHO;
}

/** Release a host object and delete it if the reference counter goes to zero.
 *
 * @param pHO      Host object which reference counter is to be decreased.
 * @return NT status.
 */
static NTSTATUS svgaHostObjectRelease(SVGAHOSTOBJECT *pHO,
                                      AVLU32TREE *pAvlTree)
{
    UINT32 const c = ASMAtomicDecU32(&pHO->cRefs);
    if (c > 0)
    {
        /* Do not delete the object. */
        return STATUS_SUCCESS;
    }

    /* Delete the object. */
    VBOXWDDM_EXT_VMSVGA *pSvga = pHO->pSvga;
    UINT32 const u32Key = pHO->u.avl.core.Key;

    KIRQL OldIrql;
    SvgaHostObjectsLock(pSvga, &OldIrql);

    SVGAHOSTOBJECT *pHORemoved = (SVGAHOSTOBJECT *)RTAvlU32Remove(pAvlTree, u32Key);

    SvgaHostObjectsUnlock(pSvga, OldIrql);

    NTSTATUS Status;
    if (pHORemoved == pHO)
    {
        if (KeGetCurrentIrql() <= APC_LEVEL)
        {
            /* Need to write to the FIFO which uses FastMutex, i.e. incompatible with DISPATCH_LEVEL+. */
            Status = pHO->pfnHostObjectDestroy
                   ? pHO->pfnHostObjectDestroy(pHO)
                   : STATUS_SUCCESS;
            GaMemFree(pHO);
        }
        else
        {
            /* This can (rarely) happen if the Dpc routine deletes a surface via SvgaRenderComplete */
            SvgaHostObjectsLock(pSvga, &OldIrql);

            pHO->u.list.u32Key = pHO->u.avl.core.Key;
            RTListAppend(&pSvga->DeletedHostObjectsList, &pHO->u.list.node);

            SvgaHostObjectsUnlock(pSvga, OldIrql);

            LOGI("Pending object sid=%u\n", pHO->u.list.u32Key);
            Status = STATUS_SUCCESS;
        }
    }
    else
    {
        LOGE("invalid parameter \n");
        Status = STATUS_INVALID_PARAMETER; /* Internal error. Should never happen. */
    }
    return Status;
}

/** Release a host object with the specified key.
 *
 * @param pSvga    Device instance.
 * @param u32Key   Host object key.
 * @return NT status.
 */
static NTSTATUS svgaHostObjectUnref(VBOXWDDM_EXT_VMSVGA *pSvga,
                                    AVLU32TREE *pAvlTree,
                                    UINT32 u32Key)
{
    SVGAHOSTOBJECT *pHO = svgaHostObjectQuery(pSvga, pAvlTree, u32Key);
    AssertPtrReturn(pHO, STATUS_INVALID_PARAMETER);
    ASMAtomicDecU32(&pHO->cRefs); /* Undo svgaHostObjectQuery */
    return svgaHostObjectRelease(pHO, pAvlTree);
}
/** Destroy the deleted objects which could not be destroyed at DISPATCH_LEVEL.
 *
 * @param pSvga    Device instance.
 * @return NT status.
 */
static NTSTATUS svgaHostObjectsProcessPending(VBOXWDDM_EXT_VMSVGA *pSvga)
{
    KIRQL OldIrql;
    SvgaHostObjectsLock(pSvga, &OldIrql);

    RTLISTANCHOR DeletedHostObjectsList;
    RTListMove(&DeletedHostObjectsList, &pSvga->DeletedHostObjectsList);

    SvgaHostObjectsUnlock(pSvga, OldIrql);

    if (RTListIsEmpty(&DeletedHostObjectsList)) /* likely */
        return STATUS_SUCCESS;

    LOGI("Deleting pending objects\n");

    SVGAHOSTOBJECT *pIter, *pNext;
    RTListForEachSafe(&DeletedHostObjectsList, pIter, pNext, SVGAHOSTOBJECT, u.list.node)
    {
        INT32 const c = pIter->cRefs;
        if (c == 0)
        {
            if (pIter->pfnHostObjectDestroy)
                pIter->pfnHostObjectDestroy(pIter);
            GaMemFree(pIter);
        }
        else
        {
            LOGE("WDDM: Deleted host object in use: cRefs %d, Key %u\n",
                  c, pIter->u.list.u32Key);
        }
    }

    LOGI("Deleting pending objects done\n");
    return STATUS_SUCCESS;
}

/** Initialize a host object.
*
 * Init the fields.
 *
 * @param pSvga    Device instance.
 * @param pHO      New host object.
 * @param uType    What kind of an object.
 * @param u32Key   Host object key.
 * @param pfnHostObjectDestroy Destructor.
 * @return NT status.
 */
static NTSTATUS svgaHostObjectInit(VBOXWDDM_EXT_VMSVGA *pSvga,
                                   SVGAHOSTOBJECT *pHO,
                                   UINT32 uType,
                                   UINT32 u32Key,
                                   PFNHostObjectDestroy pfnHostObjectDestroy)
{
    AVLU32TREE *pAvlTree;
    if (uType != SVGA_HOST_OBJECT_SURFACE) {
        LOGE("invalid parameter\n");
        return STATUS_INVALID_PARAMETER;
    }
    pAvlTree = &pSvga->SurfaceTree;

    pHO->u.avl.core.Key       = u32Key;
    pHO->cRefs                = 1;
    pHO->uType                = uType;
    pHO->pSvga                = pSvga;
    pHO->pfnHostObjectDestroy = pfnHostObjectDestroy;

    KIRQL OldIrql;
    SvgaHostObjectsLock(pSvga, &OldIrql);

    if (RTAvlU32Insert(pAvlTree, &pHO->u.avl.core))
    {
        SvgaHostObjectsUnlock(pSvga, OldIrql);
        return STATUS_SUCCESS;
    }

    SvgaHostObjectsUnlock(pSvga, OldIrql);
    return STATUS_NOT_SUPPORTED;
}

static int svgaHostObjectsDestroyCb(PAVLU32NODECORE pNode, void *pvUser)
{
    RT_NOREF(pvUser);

    SVGAHOSTOBJECT *pHO = (SVGAHOSTOBJECT *)pNode;
    if (pHO->pfnHostObjectDestroy)
        pHO->pfnHostObjectDestroy(pHO);
    GaMemFree(pHO);

    return 0;
}

NTSTATUS SvgaHostObjectsCleanup(VBOXWDDM_EXT_VMSVGA *pSvga)
{
    if (pSvga->SurfaceTree)
    {
        RTAvlU32Destroy(&pSvga->SurfaceTree, svgaHostObjectsDestroyCb, pSvga);
    }

    return svgaHostObjectsProcessPending(pSvga);
}


/*
 * SVGA surfaces.
 */

/** Surface object destructor.
 *
 * @param pHO      The corresponding host object.
 * @return NT status.
 */
static NTSTATUS svgaSurfaceObjectDestroy(SVGAHOSTOBJECT *pHO)
{
    AssertPtrReturn(pHO, STATUS_INVALID_PARAMETER);

    VBOXWDDM_EXT_VMSVGA *pSvga = pHO->pSvga;
    UINT32 const u32Sid = pHO->u.avl.core.Key;

    SURFACEOBJECT *pSO = (SURFACEOBJECT *)pHO;

    /* Delete the surface. */
    LOGI("deleted sid=%u\n", u32Sid);

    if (pSO->mobid != SVGA3D_INVALID_ID)
    {
        void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_BIND_GB_SURFACE, sizeof(SVGA3dCmdBindGBSurface), SVGA3D_INVALID_ID);
        if (pvCmd)
        {
            SVGA3dCmdBindGBSurface *pCmd = (SVGA3dCmdBindGBSurface *)pvCmd;
            pCmd->sid = u32Sid;
            pCmd->mobid = SVGA3D_INVALID_ID;
            SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdBindGBSurface));
        }
    }

    NTSTATUS Status = SvgaSurfaceDestroy(pSvga, u32Sid);
    if (NT_SUCCESS(Status))
    {
        /* Do not free the id, if the host surface deletion has failed. */
        SvgaSurfaceIdFree(pSvga, u32Sid);
    }

    // Unbind surface id from gmr region to avoid it to free wrong surface with reused surface id
    SvgaRegionUnBindSurfaceId(pSvga, pSO->mobid, u32Sid);

    return Status;
}

/** Return a SVGA surface object with the given surface id.
 *
 * @param pSvga    The device instance.
 * @param u32Sid   The surface id.
 * @return The pointer to the surface object with increased reference counter.
 */
SURFACEOBJECT *SvgaSurfaceObjectQuery(VBOXWDDM_EXT_VMSVGA *pSvga,
                                      UINT32 u32Sid)
{
    return (SURFACEOBJECT *)svgaHostObjectQuery(pSvga, &pSvga->SurfaceTree, u32Sid);
}

/** Release a surface object and delete it if the reference counter goes to zero.
 *
 * @param pSO      Surface object which reference counter is to be decreased.
 * @return NT status.
 */
NTSTATUS SvgaSurfaceObjectRelease(SURFACEOBJECT *pSO)
{
    VBOXWDDM_EXT_VMSVGA *pSvga = pSO->ho.pSvga;
    return svgaHostObjectRelease(&pSO->ho, &pSvga->SurfaceTree);
}

/** Release a surface object with the specified key.
 *
 * @param pSvga    Device instance.
 * @param u32Sid   Surface id.
 * @return NT status.
 */
NTSTATUS SvgaSurfaceUnref(VBOXWDDM_EXT_VMSVGA *pSvga,
                          UINT32 u32Sid)
{
    LOGI("sid=%u begin unref\n", u32Sid);
    return svgaHostObjectUnref(pSvga, &pSvga->SurfaceTree, u32Sid);
}

NTSTATUS SvgaSurfaceCreate(VBOXWDDM_EXT_VMSVGA *pSvga,
                           GASURFCREATE *pCreateParms,
                           GASURFSIZE *paSizes,
                           UINT32 cSizes,
                           UINT32 *pu32Sid)
{
    NTSTATUS Status = svgaHostObjectsProcessPending(pSvga);
    AssertReturn(Status == STATUS_SUCCESS, Status);

    /* A surface must have dimensions. */
    AssertReturn(cSizes >= 1, STATUS_INVALID_PARAMETER);

    /* Number of faces ('cFaces') is specified as the number of the first non-zero elements in the
     * 'pCreateParms->mip_levels' array.
     * Since only plain surfaces (cFaces == 1) and cubemaps (cFaces == 6) are supported
     * (see also SVGA3dCmdDefineSurface definition in svga3d_reg.h), we ignore anything else.
     */
    UINT32 cRemainingSizes = cSizes;
    UINT32 cFaces = 0;
    for (UINT32 i = 0; i < RT_ELEMENTS(pCreateParms->mip_levels); ++i)
    {
        if (pCreateParms->mip_levels[i] == 0)
            break;

        /* Can't have too many mip levels. */
        AssertReturn(pCreateParms->mip_levels[i] <= pSvga->u32MaxTextureLevels, STATUS_INVALID_PARAMETER);

        /* All SVGA3dSurfaceFace structures must have the same value of numMipLevels field */
        AssertReturn(pCreateParms->mip_levels[i] == pCreateParms->mip_levels[0], STATUS_INVALID_PARAMETER);

        /* The miplevels count value can't be greater than the number of remaining elements in the paSizes array. */
        AssertReturn(pCreateParms->mip_levels[i] <= cRemainingSizes, STATUS_INVALID_PARAMETER);
        cRemainingSizes -= pCreateParms->mip_levels[i];

        ++cFaces;
    }
    for (UINT32 i = cFaces; i < RT_ELEMENTS(pCreateParms->mip_levels); ++i)
        AssertReturn(pCreateParms->mip_levels[i] == 0, STATUS_INVALID_PARAMETER);

    /* cFaces must be 6 for a cubemap and 1 otherwise. */
    AssertReturn(cFaces == (UINT32)((pCreateParms->flags & SVGA3D_SURFACE_CUBEMAP) ? 6 : 1), STATUS_INVALID_PARAMETER);

    /* Sum of pCreateParms->mip_levels[i] must be equal to cSizes. */
    AssertReturn(cRemainingSizes == 0, STATUS_INVALID_PARAMETER);

   SURFACEOBJECT *pSO = (SURFACEOBJECT *)GaMemAllocZero(sizeof(SURFACEOBJECT));
    if (pSO)
    {
        UINT32 u32Sid;
        Status = SvgaSurfaceIdAlloc(pSvga, &u32Sid);
        if (NT_SUCCESS(Status))
        {
            Status = SvgaSurfaceDefine(pSvga, pCreateParms, paSizes, cSizes, u32Sid);
            if (NT_SUCCESS(Status))
            {
                pSO->u32SharedSid = u32Sid; /* Initially. The user mode driver can change this for shared surfaces. */
                pSO->mobid = SVGA3D_INVALID_ID;

                Status = svgaHostObjectInit(pSvga, &pSO->ho, SVGA_HOST_OBJECT_SURFACE, u32Sid, svgaSurfaceObjectDestroy);
                if (NT_SUCCESS(Status))
                {
                    *pu32Sid = u32Sid;

                    LOGI("created sid=%u\n", u32Sid);
                    return STATUS_SUCCESS;
                }

                AssertFailed();

                /*
                 * Cleanup on error.
                 */
                SvgaSurfaceDestroy(pSvga, u32Sid);
            }
            SvgaSurfaceIdFree(pSvga, u32Sid);
        }
        GaMemFree(pSO);
    }
    else
        Status = STATUS_INSUFFICIENT_RESOURCES;

    LOGE("surface create failed");
    return Status;
}

static NTSTATUS svgaGBSurfaceDefine(VBOXWDDM_EXT_VMSVGA *pSvga,
                                    UINT32 u32Sid,
                                    SVGAGBSURFCREATE *pCreateParms,
                                    UINT32 mobid)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_DEFINE_GB_SURFACE_V4, sizeof(SVGA3dCmdDefineGBSurface_v4), SVGA3D_INVALID_ID);
    if (pvCmd)
    {
        SVGA3dCmdDefineGBSurface_v4 *pCmd = (SVGA3dCmdDefineGBSurface_v4 *)pvCmd;
        pCmd->sid              = u32Sid;
        pCmd->surfaceFlags     = pCreateParms->s.flags;
        pCmd->format           = pCreateParms->s.format;
        pCmd->numMipLevels     = pCreateParms->s.numMipLevels;
        pCmd->multisampleCount = pCreateParms->s.sampleCount;
        pCmd->autogenFilter    = SVGA3D_TEX_FILTER_NONE;
        pCmd->size             = pCreateParms->s.size;
        pCmd->arraySize        = pCreateParms->s.numFaces;
        pCmd->bufferByteStride = 0;
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdDefineGBSurface_v4));
    }
    else
        return STATUS_INSUFFICIENT_RESOURCES;

    pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_BIND_GB_SURFACE, sizeof(SVGA3dCmdBindGBSurface), SVGA3D_INVALID_ID);
    if (pvCmd)
    {
        SVGA3dCmdBindGBSurface *pCmd = (SVGA3dCmdBindGBSurface *)pvCmd;
        pCmd->sid = u32Sid;
        pCmd->mobid = mobid;
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdBindGBSurface));
    }
    else
        return STATUS_INSUFFICIENT_RESOURCES;

    return STATUS_SUCCESS;
}

static void svgaGBSurfaceDestroy(VBOXWDDM_EXT_VMSVGA *pSvga, UINT32 u32Sid)
{
    SvgaSurfaceDestroy(pSvga, u32Sid);
}

NTSTATUS SvgaGBSurfaceCreate(VBOXWDDM_EXT_VMSVGA *pSvga,
                             void *pvOwner,
                             SVGAGBSURFCREATE *pCreateParms)
{
    NTSTATUS Status = svgaHostObjectsProcessPending(pSvga);
    AssertReturn(Status == STATUS_SUCCESS, Status);

    LOGI("gmrid = %u\n", pCreateParms->gmrid);

    UINT32 cbGB = 0;
    UINT64 u64UserAddress = 0;
    bool create_region = false;
    /* Allocate GMR, if not already supplied. */
    if (pCreateParms->gmrid == SVGA3D_INVALID_ID)
    {
        create_region = true;
        UINT32 u32NumPages = (pCreateParms->cbGB + PAGE_SIZE - 1) >> PAGE_SHIFT;
        Status = SvgaRegionCreate(pSvga, pvOwner, u32NumPages, &pCreateParms->gmrid, &u64UserAddress);
        if (!NT_SUCCESS(Status)) {
            LOGE("Region create failed: cAllocatedGmrs=%u, cAllocatedGmrPages=%u\n", pSvga->cAllocatedGmrs, pSvga->cAllocatedGmrPages);
            return Status;
        }
        cbGB = u32NumPages * PAGE_SIZE;
    }
    else
    {
        Status = SvgaRegionUserAddressAndSize(pSvga, pCreateParms->gmrid, &u64UserAddress, &cbGB);
        if (!NT_SUCCESS(Status)) {
            LOGE("get region user address failed\n");
            return Status;
        }
    }

    SURFACEOBJECT *pSO = (SURFACEOBJECT *)GaMemAllocZero(sizeof(SURFACEOBJECT));
    if (!pSO) {
        LOGE("alloc surface obj failed\n");
        if (create_region) {
            SvgaRegionDestroy(pSvga, pCreateParms->gmrid);
        }
        return STATUS_INSUFFICIENT_RESOURCES;
    }

    UINT32 u32Sid;
    Status = SvgaSurfaceIdAlloc(pSvga, &u32Sid);
    if (NT_SUCCESS(Status))
    {
        Status = svgaGBSurfaceDefine(pSvga, u32Sid, pCreateParms, pCreateParms->gmrid);
        if (NT_SUCCESS(Status))
        {
            pSO->u32SharedSid = u32Sid; /* Initially. The user mode driver can change this for shared surfaces. */
            pSO->mobid = pCreateParms->gmrid;

            Status = svgaHostObjectInit(pSvga, &pSO->ho, SVGA_HOST_OBJECT_SURFACE, u32Sid, svgaSurfaceObjectDestroy);
            if (NT_SUCCESS(Status))
            {
                pCreateParms->cbGB = cbGB;
                pCreateParms->u64UserAddress = u64UserAddress;
                pCreateParms->u32Sid = u32Sid;

                SvgaRegionBindSurfaceId(pSvga, pCreateParms->gmrid, u32Sid);

                LOGI("created sid=%u\n", u32Sid);
                return STATUS_SUCCESS;
            }
            AssertFailed();
            /*
             * Cleanup on error.
             */
            svgaGBSurfaceDestroy(pSvga, u32Sid);
        }
        SvgaSurfaceIdFree(pSvga, u32Sid);
    } else {
        LOGE("alloc surface id failed\n");
    }

    LOGE("surface create failed\n");
    if (create_region) {
        SvgaRegionDestroy(pSvga, pCreateParms->gmrid);
    }

    GaMemFree(pSO);

    return Status;
}

NTSTATUS SvgaCreateDecoder(VBOXWDDM_EXT_VMSVGA *pSvga,
                           UINT32 pDecoderId,
                           VBoxVideoDecoderDesc *desc,
                           VBoxVideoDecoderConfigPic *pic,
                           UINT32 pCid)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_CREATE_DECODE_DEVICE,
                                         sizeof(SVGA3dCmdCreateDecodeDevice), SVGA3D_INVALID_ID);
    if (pvCmd) {
        SVGA3dCmdCreateDecodeDevice* pCmd = (SVGA3dCmdCreateDecodeDevice*)pvCmd;
        pCmd->pDecoderId = pDecoderId;
        pCmd->pCid = pCid;
        memcpy(&pCmd->desc, desc, sizeof(VBoxVideoDecoderDesc));
        memcpy(&pCmd->pic, pic, sizeof(VBoxVideoDecoderConfigPic));
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdCreateDecodeDevice));
    } else
        return STATUS_INSUFFICIENT_RESOURCES;

    // change vsync frequency to 120 when decoder is working
    ASMAtomicIncU32(&g_DecoderCnt);
    g_RefreshRate = VBOXWDDM_DECODER_REFRESH_RATE;
    return STATUS_SUCCESS;
}

NTSTATUS SvgaDestroyDecoder(VBOXWDDM_EXT_VMSVGA *pSvga, UINT32 pu32DecoderId)
{
    // change vsync frequency to 60 when decoder is not working
    if (ASMAtomicDecU32(&g_DecoderCnt) == 0) {
        g_RefreshRate = VBOXWDDM_DEFAULT_REFRESH_RATE;
    }

    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_DECODE_DESTROY,
                                         sizeof(SVGA3dCmdDestroyDecoder), SVGA3D_INVALID_ID);
    if (pvCmd) {
        SVGA3dCmdDestroyDecoder *pCmd = (SVGA3dCmdDestroyDecoder*)pvCmd;
        pCmd->decoderId = pu32DecoderId;
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdDestroyDecoder));
    }
    else
        return STATUS_INSUFFICIENT_RESOURCES;

    return STATUS_SUCCESS;
}

NTSTATUS SvgaSetDecoderRT(VBOXWDDM_EXT_VMSVGA *pSvga,
                          UINT32 pu32DecoderId,
                          UINT32 pu32Sid,
                          UINT32 pu32SubIndex)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_SET_DECODE_RT, sizeof(SVGA3dCmdSetDecodeRT),
                                         SVGA3D_INVALID_ID);
    if (pvCmd) {
        SVGA3dCmdSetDecodeRT *pCmd = (SVGA3dCmdSetDecodeRT*)pvCmd;
        pCmd->decoderId = pu32DecoderId;
        pCmd->sid = pu32Sid;
        pCmd->subIndex = pu32SubIndex;
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdSetDecodeRT));
    } else
        return STATUS_INSUFFICIENT_RESOURCES;
    return STATUS_SUCCESS;
}

NTSTATUS SvgaSetDecoderBeginFrame(VBOXWDDM_EXT_VMSVGA *pSvga,
                                  UINT32 pu32DecoderId)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_DECODE_BEGINFRAME,
                                         sizeof(SVGA3dCmdDecodeBeginFrame), SVGA3D_INVALID_ID);
    if (pvCmd) {
        SVGA3dCmdDecodeBeginFrame *pCmd = (SVGA3dCmdDecodeBeginFrame*)pvCmd;
        pCmd->decoderId = pu32DecoderId;
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdDecodeBeginFrame));
    } else
        return STATUS_INSUFFICIENT_RESOURCES;
    return STATUS_SUCCESS;
}

NTSTATUS SvgaSetDecoderEndFrame(VBOXWDDM_EXT_VMSVGA *pSvga,
                                UINT32 pu32DecoderId)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_DECODE_ENDFRAME, sizeof(SVGA3dCmdDecodeEndFrame),
                                         SVGA3D_INVALID_ID);
    if (pvCmd) {
        SVGA3dCmdDecodeEndFrame *pCmd = (SVGA3dCmdDecodeEndFrame*)pvCmd;
        pCmd->decoderId = pu32DecoderId;
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdDecodeEndFrame));
    } else
        return STATUS_INSUFFICIENT_RESOURCES;
    return STATUS_SUCCESS;
}

NTSTATUS SvgaSetDecoderExecute(VBOXWDDM_EXT_VMSVGA *pSvga,
                               UINT32 pu32DecoderId,
                               UINT32 bufferCount,
                               VBOXDISPIFSCAPE_VIDEODECODERBUFFERDESC* bufferDesc)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_DECODE_EXECUTE,
                                         sizeof(VBOXDISPIFSCAPE_VIDEODECODERBUFFERDESC) * bufferCount +
                                         sizeof(UINT32) * 2, SVGA3D_INVALID_ID);
    if (pvCmd) {
        memcpy(pvCmd, &pu32DecoderId, sizeof(UINT32));
        memcpy((UINT8 *)pvCmd + sizeof(UINT32), &bufferCount, sizeof(UINT32));
        memcpy((UINT8 *)pvCmd + sizeof(UINT32) * 2, (uint8_t*)bufferDesc,
               sizeof(VBOXDISPIFSCAPE_VIDEODECODERBUFFERDESC) * bufferCount);
        SvgaCmdBufCommit(pSvga, sizeof(VBOXDISPIFSCAPE_VIDEODECODERBUFFERDESC) * bufferCount + sizeof(UINT32) * 2);
    } else
        return STATUS_INSUFFICIENT_RESOURCES;
    return STATUS_SUCCESS;
}

NTSTATUS SvgaDecodeSurfaceInfo(VBOXWDDM_EXT_VMSVGA *pSvga,
                               UINT32 puSurfaceCount,
                               UINT32* surfaceInfo)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_DECODE_SURFACEINFO, sizeof(UINT32) * (puSurfaceCount + 1),
                                         SVGA3D_INVALID_ID);
    if (pvCmd) {
        memcpy(pvCmd, &puSurfaceCount, sizeof(UINT32));
        memcpy((UINT8 *)pvCmd + sizeof(UINT32), surfaceInfo, sizeof(UINT32) * puSurfaceCount);
        SvgaCmdBufCommit(pSvga, sizeof(UINT32) * (puSurfaceCount + 1));
    }
    else
        return STATUS_INSUFFICIENT_RESOURCES;
    return STATUS_SUCCESS;
}

NTSTATUS SvgaStretchRetc(VBOXWDDM_EXT_VMSVGA *pSvga,
                         UINT32 uSrcSid,
                         UINT32 uDstSid,
                         RECT StcRect,
                         RECT DstRect,
                         UINT32 u32Cid)
{
    void *pvCmd = SvgaCmdBuf3dCmdReserve(pSvga, SVGA_3D_CMD_STRETCHRETC, sizeof(SVGA3dCmdStretchRect),
                                         SVGA3D_INVALID_ID);
    if (pvCmd) {
        SVGA3dCmdStretchRect*pCmd = (SVGA3dCmdStretchRect*)pvCmd;
        pCmd->u32Cid = u32Cid;
        pCmd->uSrcSid = uSrcSid;
        pCmd->uDstSid = uDstSid;
        pCmd->SrcRect.left = StcRect.left;
        pCmd->SrcRect.right = StcRect.right;
        pCmd->SrcRect.top = StcRect.top;
        pCmd->SrcRect.bottom = StcRect.bottom;
        pCmd->DstRect.left = DstRect.left;
        pCmd->DstRect.right = DstRect.right;
        pCmd->DstRect.top = DstRect.top;
        pCmd->DstRect.bottom = DstRect.bottom;
        SvgaCmdBufCommit(pSvga, sizeof(SVGA3dCmdStretchRect));
    }
    else
        return STATUS_INSUFFICIENT_RESOURCES;
    return STATUS_SUCCESS;
}
